Report finite-typed values in a constant array haystack that can never be the in_array()/array_search()/array_keys() needle - #6054
Merged
ondrejmirtes merged 2 commits intoJul 15, 2026
Conversation
…r be the in_array()/array_search()/array_keys() needle - Add ImpossibleInArrayHaystackFiniteTypesRule (level 4) that iterates the haystack via Type::getConstantArrays() and reports every value whose Type::getFiniteTypes() is non-empty but can never be the needle, instead of inspecting the Array_ AST node. - Determine "can never match" through InitializerExprTypeResolver::resolveIdenticalType() for strict comparisons and resolveEqualType() for loose ones, matching PHP's runtime === / == semantics (a maybe-strict third argument requires both to be false). - Skip reporting for in_array() when no haystack value can match at all, since that whole-call impossibility is already reported by ImpossibleCheckTypeFunctionCallRule. - Handle the analogous array_search() (same needle/haystack argument positions) and array_keys() search form (needle and haystack swapped) with the same logic.
ondrejmirtes
requested changes
Jul 15, 2026
ondrejmirtes
left a comment
Member
There was a problem hiding this comment.
Only in bleeding edge on level 4 pls. Can't have RegisteredRule attribute yet, needs to be registered as a service in config.level4.neon + rule tag applied in conditionalTags.
… level 4 Register ImpossibleInArrayHaystackFiniteTypesRule as a service in config.level4.neon with a conditionalTags entry tied to the new featureToggles.finiteTypesInHaystack toggle (on in bleedingEdge.neon) instead of the #[RegisteredRule] attribute, so the rule only runs in bleeding edge for now. The treatPhpDocTypesAsCertain argument is passed explicitly since #[AutowiredParameter] is not processed for manually registered services. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collaborator
Author
|
Done. I've addressed the review by moving the rule from an always-on Changes made
Config wiring (the reviewer's requested pattern, mirroring
Verification
Committed and pushed to |
ondrejmirtes
approved these changes
Jul 15, 2026
Contributor
|
CI fails in PHP 8.0/7.4 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
in_array(),array_search()andarray_keys()searched a constant array haystack that contained finite-typed values (enum cases, constant scalars, whole enums, small int ranges, …) which can never be the needle, yet PHPStan reported nothing as long as at least one other value in the haystack could match. For examplein_array($int, [Foo::ONE, 1, 2], true)silently kept the deadFoo::ONEentry.This adds a rule that reports each finite-typed haystack value that can never be the needle, using the type system (
Type::getConstantArrays()+Type::getFiniteTypes()) rather than the array literal AST, as requested in the issue.Changes
src/Rules/Comparison/ImpossibleInArrayHaystackFiniteTypesRule.php, registered at level 4 via#[RegisteredRule(level: 4)].treatPhpDocTypesAsCertain), enumerates every constant array of the haystack, and for each value type whosegetFiniteTypes()is non-empty checks whether it can ever equal the needle.InitializerExprTypeResolver::resolveIdenticalType()(strict) /resolveEqualType()(loose), so PHP's===/==semantics are respected; an unknown (maybe) third argument requires both to be impossible.in_array(), if no value can match at all the rule stays silent so the whole-call impossibility keeps being reported once byImpossibleCheckTypeFunctionCallRule(no double reporting).ImpossibleInArrayHaystackFiniteTypesRuleTest+ data filedata/impossible-in-array-finite-types.php.Root cause
The existing
InArrayFunctionTypeSpecifyingExtensiononly narrows the whole call and only produces an impossible-type error when the entirein_array()evaluates tofalse. A partially-dead haystack (some values can match, some finite values never can) was invisible. The fix moves the check to the value level and is driven purely by the type system, so it works for any constant-array haystack regardless of whether it comes from a literal, a variable, or a class constant.Analogous cases probed and fixed
The bug sits on the "needle/haystack search function" axis:
array_search()— same(needle, haystack, strict)signature — fixed (was silent). It has no companion impossible-type rule, so it reports even when the whole call is impossible.array_keys($array, $filter_value, $strict)— search form with the needle and haystack arguments swapped — fixed (was silent). The rule uses a per-function argument-position map.in_array()whole-call impossibility — already covered byImpossibleCheckTypeFunctionCallRule; the new rule deliberately defers to it to avoid duplicate errors.Test
ImpossibleInArrayHaystackFiniteTypesRuleTestcovers, for aFooenum:in_array($int, [Foo::ONE, 1, 2])(reportsFoo::ONE, with "identical to"/"equal to" wording),array_search()andarray_keys()equivalents,Foo::ONEwith haystack[Foo::ONE, Foo::TWO](reports the deadFoo::TWO),mixedneedle, whole-call-impossiblein_array()(handled elsewhere), non-constant haystack, and a union needle that still matches.Fixes phpstan/phpstan#14960